
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

/** Patching program. Very simple. */

// We have had yet more evidence that this will be needed in future, as the
// complexity of the patching system increases to the point that using
// command line tools that might not exist causes real headaches when the
// wrong parts of the executable are patched.

/** Need to in future add dealing with symbols marked as OS-specific. */

typedef uint32_t Elf32_Addr;
typedef uint16_t Elf32_Half;
typedef uint32_t Elf32_Off;
typedef int32_t  Elf32_Sword;
typedef uint32_t Elf32_Word;

typedef uint64_t Elf64_Addr;
typedef uint64_t Elf64_Off;
typedef uint16_t Elf64_Half;
typedef uint32_t Elf64_Word;
typedef  int32_t Elf64_Sword;
typedef uint64_t Elf64_Xword;
typedef  int64_t Elf64_Sxword;
typedef  int16_t Elf64_Section;

typedef struct elfinfo
{
   int machinebits;
   uint64_t section_header_table_offset;
   uint64_t section_header_size;
   uint64_t section_header_count;
   uint64_t section_index_section_names;
   uint64_t section_names_offset;
} ElfInfo;

int hasElfMagic(FILE *FP)
{
   int iself = 1;
   char keep;
   fseek(FP, 0, SEEK_SET);
   fread(&keep, 1, 1, FP);
   iself &= (keep == 0x7f);
   fread(&keep, 1, 1, FP);
   iself &= (keep == 'E');
   fread(&keep, 1, 1, FP);
   iself &= (keep == 'L');
   fread(&keep, 1, 1, FP);
   iself &= (keep == 'F');
   return iself;
}

int ensureFileIsSYSV(FILE *FP, int writable)
{
   if (hasElfMagic(FP))
   {
      fseek(FP, 7, SEEK_SET);
      char typechar;
      fread(&typechar, 1, 1, FP);
      if (typechar != 0)
      {
         fprintf(stderr, "file was not SYSV (0), was type %d\n", typechar);
         if (!writable)
         {
            fprintf(stderr, "not writable, so not patching, but continuing anyway...\n");
            return 1;
         }
         fprintf(stderr, "patching from type (%d), to SYSV (0)\n", typechar);
         fseek(FP, 7, SEEK_SET);
         typechar = 0;
         fwrite(&typechar, 1, 1, FP);
      }
      return 1;
   } else
      return 0;
}

void setMachineBits(FILE *FP, ElfInfo *elfi)
{
   char keep;
   fseek(FP, 4, SEEK_SET);
   fread(&keep, 1, 1, FP);
   switch (keep)
   {
      case 0x1: elfi->machinebits = 32; break;
      case 0x2: elfi->machinebits = 64; break;
      default: elfi->machinebits = 0; break;
   }
   if (elfi->machinebits == 0)
      fprintf(stderr, "ELF CLASS invalid (neither 32 or 64)\n");
}

void getSectionHeaderTable(FILE *FP, ElfInfo *elfi)
{
   switch (elfi->machinebits)
   {
      case 32:
      {
         Elf32_Off off32;
         fseek(FP, 16 + sizeof(Elf32_Half)*2 + sizeof(Elf32_Word) + sizeof(Elf32_Addr) + sizeof(Elf32_Off), SEEK_SET);
         fread(&off32, sizeof(Elf32_Off), 1, FP);
         elfi->section_header_table_offset = off32;
         fseek(FP, sizeof(Elf32_Word) + sizeof(Elf32_Half)*3, SEEK_CUR);
         Elf32_Half sh_size;
         Elf32_Half sh_ents;
         Elf32_Half sh_names;
         fread(&sh_size, sizeof(Elf32_Half), 1, FP);
         fread(&sh_ents, sizeof(Elf32_Half), 1, FP);
         fread(&sh_names, sizeof(Elf32_Half), 1, FP);
         elfi->section_header_size = sh_size;
         elfi->section_header_count = sh_ents;
         elfi->section_index_section_names = sh_names;
         break;
      }
      case 64:
      {
         Elf64_Off off64;
         fseek(FP, 16 + sizeof(Elf64_Half)*2 + sizeof(Elf64_Word) + sizeof(Elf64_Addr) + sizeof(Elf64_Off), SEEK_SET);
         fread(&off64, sizeof(Elf64_Off), 1, FP);
         elfi->section_header_table_offset = off64;
         fseek(FP, sizeof(Elf64_Word) + sizeof(Elf64_Half)*3, SEEK_CUR);
         Elf64_Half sh_size;
         Elf64_Half sh_ents;
         Elf64_Half sh_names;
         fread(&sh_size, sizeof(Elf64_Half), 1, FP);
         fread(&sh_ents, sizeof(Elf64_Half), 1, FP);
         fread(&sh_names, sizeof(Elf64_Half), 1, FP);
         elfi->section_header_size = sh_size;
         elfi->section_header_count = sh_ents;
         elfi->section_index_section_names = sh_names;
         break;
      }
      default:
         fprintf(stderr, "cannot parse header structure with no knowledge of machine bits\n");
         elfi->section_header_table_offset = 0;
         elfi->section_header_size = 0;
         elfi->section_header_count = 0;
         elfi->section_index_section_names = 0;
         break;
   }
}

uint64_t findSectionOffset(FILE *FP, ElfInfo *elfi, int section_number)
{
   if (elfi->section_header_table_offset == 0) return;
   uint64_t extra_offset = 0;
   switch (elfi->machinebits)
   {
      case 32:
      {
         Elf32_Off offs = 0;
         fseek(FP,
            elfi->section_header_table_offset + (elfi->section_header_size * section_number) +
            sizeof(Elf32_Word) * 3 + sizeof(Elf32_Addr), SEEK_SET);
         fread(&offs, sizeof(Elf32_Off), 1, FP);
         return (uint64_t)offs;
      }
      case 64:
      {
         Elf64_Off offs = 0;
         fseek(FP,
            elfi->section_header_table_offset + (elfi->section_header_size * section_number) +
            sizeof(Elf64_Word) * 2 + sizeof(Elf64_Off) + sizeof(Elf64_Addr), SEEK_SET);
         fread(&offs, sizeof(Elf64_Off), 1, FP);
         return (uint64_t)offs;
      }
      default:
      {
         fprintf(stderr, "machine bits not defined trying to find section names offset\n");
         return 0;
      }
   }
}

char *strdup_fromSectionWithOffset(FILE *FP, ElfInfo *elfi, uint64_t sect_off, uint64_t offset)
{
   fseek(FP, sect_off + offset, SEEK_SET);
   char curr_char = ' ';
   int tcount = 0;
   while (curr_char != '\0')
   {
      fread(&curr_char, 1, 1, FP);
      tcount++;
   }
   fseek(FP, -tcount, SEEK_CUR);
   char *tstring = (char *)malloc(sizeof(char)*tcount);
   fread(tstring, 1, tcount, FP);
   return tstring;
}

int findSection(FILE *FP, ElfInfo *elfi, const char *section_name)
{
   int section_no = -1;
   int i;
   uint64_t string_table_offset = 0;
   if (elfi->section_header_table_offset == 0) return;
   for (i = 0; i < (elfi->section_header_count); i++)
   {
      fseek(FP, elfi->section_header_table_offset + i * elfi->section_header_size, SEEK_SET);
      switch (elfi->machinebits)
      {
         case 32:
         {
            Elf32_Word sh_name_string;
            fread(&sh_name_string, sizeof(Elf32_Word), 1, FP);
            string_table_offset = sh_name_string;
            break;
         }
         case 64:
         {
            Elf64_Word sh_name_string;
            fread(&sh_name_string, sizeof(Elf64_Word), 1, FP);
            string_table_offset = sh_name_string;
            break;
         }
         default:
         {
            fprintf(stderr, "machine bits not defined in section finding error\n");
            break;
         }
      }
      fseek(FP, elfi->section_names_offset + string_table_offset, SEEK_SET);
      char my_k = ' ';
      int tcount = 0;
      while (my_k != '\0')
      {
         fread(&my_k, 1, 1, FP);
         tcount++;
      }
      fseek(FP, -tcount, SEEK_CUR);
      char *tsection_name = (char *)malloc(sizeof(char)*tcount);
      fread(tsection_name, 1, tcount, FP);
      if (strcmp(section_name, tsection_name) == 0)
         section_no = i;
      free(tsection_name);
   }
   return section_no;
}

uint64_t getValueFromDynamic(FILE *FP, ElfInfo *elfi, uint32_t tagno)
{
   int dynamic_section;
   dynamic_section = findSection(FP, elfi, ".dynamic");
   uint64_t dynamic_section_offset;
   dynamic_section_offset = findSectionOffset(FP, elfi, dynamic_section);
   int countidx = 0;
   uint32_t tag_id = 1;
   fseek(FP, dynamic_section_offset, SEEK_SET);
   uint64_t d_tag;
   uint64_t d_un;
   while (tag_id != 0)
   {
      switch (elfi->machinebits)
      {
         case 32:
         {
            Elf32_Sword d_tag_32;
            Elf32_Addr d_un_32;
            fread(&d_tag_32, sizeof(Elf32_Sword), 1, FP);
            fread(&d_un_32, sizeof(Elf32_Addr), 1, FP);
            d_tag = d_tag_32;
            d_un = d_un_32;
            break;
         }
         case 64:
         {
            Elf64_Xword d_tag_64;
            Elf64_Addr d_un_64;
            fread(&d_tag_64, sizeof(Elf64_Xword), 1, FP);
            fread(&d_un_64, sizeof(Elf64_Addr), 1, FP);
            d_tag = d_tag_64;
            d_un = d_un_64;
            break;
         }
         default:
            fprintf(stderr, "error badly set machine bits in get from dynamic section\n");
            d_tag = 0;
            break;
      }
      if (d_tag == tagno)
         return d_un;
      tag_id = (uint32_t)d_tag;
   }
   fprintf(stderr, "did not find tag with value 0x%.08X in .dynamic section\n", tagno);
   return -1;
}

int findSectionLinkedStringSection(FILE *FP, ElfInfo *elfi, int section_number)
{
   if (elfi->section_header_table_offset == 0) return;
   uint64_t extra_offset = 0;
   switch (elfi->machinebits)
   {
      case 32:
      {
         Elf32_Word offs = 0;
         fseek(FP,
            elfi->section_header_table_offset + (elfi->section_header_size * section_number) +
            sizeof(Elf32_Word) * 4 + sizeof(Elf32_Addr) + sizeof(Elf32_Off), SEEK_SET);
         fread(&offs, sizeof(Elf32_Word), 1, FP);
         return (uint64_t)offs;
      }
      case 64:
      {
         Elf64_Word offs = 0;
         fseek(FP,
            elfi->section_header_table_offset + (elfi->section_header_size * section_number) +
            sizeof(Elf64_Word) * 2 + sizeof(Elf64_Xword) * 2 + sizeof(Elf64_Off) + sizeof(Elf64_Addr), SEEK_SET);
         fread(&offs, sizeof(Elf64_Word), 1, FP);
         return (uint64_t)offs;
      }
      default:
      {
         fprintf(stderr, "machine bits not defined trying to find section names offset\n");
         return 0;
      }
   }
}

char **getAndPatchMatchingVersions(FILE *FP, ElfInfo *elfi, uint64_t verneed_offset,
   int verneed_count, uint64_t strings_offset, const char **version_strings, int patchflag)
{
   int i, j;
   int count_input_strings = 0;
   if (version_strings)
      while (version_strings[count_input_strings]) count_input_strings++;
   char **versions_list = (char **)malloc(sizeof(char *)*(count_input_strings+1));
   int verneed_block = 0;
   switch (elfi->machinebits)
   {
      case 32: verneed_block = sizeof(Elf32_Half) * 2 + sizeof(Elf32_Word) * 3;
      case 64: verneed_block = sizeof(Elf64_Half) * 2 + sizeof(Elf64_Word) * 3;
      default: break;
   }
   versions_list[0] = NULL;
   uint64_t last_verneed_offset = 0;
   uint64_t last_vernaux_offset = 0;
   for (i = 0; i < verneed_count; i++)
   {
      uint64_t vn_version;
      uint64_t vn_cnt;
      uint64_t vn_file;
      fseek(FP, verneed_offset + last_verneed_offset, SEEK_SET);
      switch (elfi->machinebits)
      {
         case 32:
         {
            Elf32_Half vn_version_32;
            Elf32_Half vn_cnt_32;
            Elf32_Word vn_file_32;
            Elf32_Word vn_aux_32;
            Elf32_Word vn_next_32;
            fread(&vn_version_32, sizeof(Elf32_Half), 1, FP);
            fread(&vn_cnt_32, sizeof(Elf32_Half), 1, FP);
            fread(&vn_file_32, sizeof(Elf32_Word), 1, FP);
            fread(&vn_aux_32, sizeof(Elf32_Word), 1, FP);
            fread(&vn_next_32, sizeof(Elf32_Word), 1, FP);
            vn_version = vn_version_32;
            vn_cnt = vn_cnt_32;
            vn_file = vn_file_32;
            last_vernaux_offset = last_verneed_offset + vn_aux_32;
            last_verneed_offset += vn_next_32;
            break;
         }
         case 64:
         {
            Elf64_Half vn_version_64;
            Elf64_Half vn_cnt_64;
            Elf64_Word vn_file_64;
            Elf64_Word vn_aux_64;
            Elf64_Word vn_next_64;
            fread(&vn_version_64, sizeof(Elf64_Half), 1, FP);
            fread(&vn_cnt_64, sizeof(Elf64_Half), 1, FP);
            fread(&vn_file_64, sizeof(Elf64_Word), 1, FP);
            fread(&vn_aux_64, sizeof(Elf64_Word), 1, FP);
            fread(&vn_next_64, sizeof(Elf64_Word), 1, FP);
            vn_version = vn_version_64;
            vn_cnt = vn_cnt_64;
            vn_file = vn_file_64;
            last_vernaux_offset = last_verneed_offset + vn_aux_64;
            last_verneed_offset += vn_next_64;
            break;
         }
         default:
            fprintf(stderr, "Machine bits wrong in verneed array traversal\n");
            break;
      }
      char *vn_name_dupl = strdup_fromSectionWithOffset(FP, elfi, strings_offset, vn_file);
      fprintf(stderr, "from file: %s\n", vn_name_dupl);
      free(vn_name_dupl);
      for (j = 0; j < vn_cnt; j++)
      {
         uint64_t vna_hash;
         uint64_t vna_flags;
         uint64_t vna_other;
         uint64_t vna_name;
         fseek(FP, verneed_offset + last_vernaux_offset, SEEK_SET);
         switch (elfi->machinebits)
         {
            case 32:
            {
               Elf32_Word vna_hash_32;
               Elf32_Half vna_flags_32;
               Elf32_Half vna_other_32;
               Elf32_Word vna_name_32;
               Elf32_Word vna_next_32;
               fread(&vna_hash_32, sizeof(Elf32_Word), 1, FP);
               fread(&vna_flags_32, sizeof(Elf32_Half), 1, FP);
               fread(&vna_other_32, sizeof(Elf32_Half), 1, FP);
               fread(&vna_name_32, sizeof(Elf32_Word), 1, FP);
               fread(&vna_next_32, sizeof(Elf32_Word), 1, FP);
               vna_hash = vna_hash_32;
               vna_flags = vna_flags_32;
               vna_other = vna_other_32;
               vna_name = vna_name_32;
               last_vernaux_offset += vna_next_32;
               break;
            }
            case 64:
            {
               Elf64_Word vna_hash_64;
               Elf64_Half vna_flags_64;
               Elf64_Half vna_other_64;
               Elf64_Word vna_name_64;
               Elf64_Word vna_next_64;
               fread(&vna_hash_64, sizeof(Elf64_Word), 1, FP);
               fread(&vna_flags_64, sizeof(Elf64_Half), 1, FP);
               fread(&vna_other_64, sizeof(Elf64_Half), 1, FP);
               fread(&vna_name_64, sizeof(Elf64_Word), 1, FP);
               fread(&vna_next_64, sizeof(Elf64_Word), 1, FP);
               vna_hash = vna_hash_64;
               vna_flags = vna_flags_64;
               vna_other = vna_other_64;
               vna_name = vna_name_64;
               last_vernaux_offset += vna_next_64;
               break;
            }
            default:
               fprintf(stderr, "Machine bits not set trying to traverse vernaux array\n");
               break;
         }
         char *vna_name_dupl = strdup_fromSectionWithOffset(FP, elfi, strings_offset, vna_name);
         int tcount2 = 0;
         int patching = 0;
         while (version_strings[tcount2])
         {
            if (strcmp(version_strings[tcount2], vna_name_dupl) == 0)
            {
               if (patchflag != 0)
               {
                  patching = 1;
                  switch (elfi->machinebits)
                  {
                     case 32:
                     {
                        fseek(FP, SEEK_CUR, -(int64_t)(sizeof(Elf32_Word) * 2 + sizeof(Elf32_Half) * 2));
                        Elf32_Half vna_flags_32 = patchflag;
                        fwrite(&vna_flags_32, sizeof(Elf32_Half), 1, FP);
                        break;
                     }
                     case 64:
                     {
                        fseek(FP, SEEK_CUR, -(int64_t)(sizeof(Elf64_Word) * 2 + sizeof(Elf64_Half) * 2));
                        Elf64_Half vna_flags_64 = patchflag;
                        fwrite(&vna_flags_64, sizeof(Elf64_Half), 1, FP);
                        break;
                     }
                     default:
                     {
                        fprintf(stderr, "Tried to write flags to versioned symbols without a valid ELF class\n");
                        break;
                     }
                  }
               }
               int tcount3 = 0;
               int already_have_flag = 0;
               while (versions_list[tcount3])
               {
                  already_have_flag |= (strcmp(versions_list[tcount3], vna_name_dupl) == 0);
                  tcount3++;
               }
               if (already_have_flag == 0)
               {
                  versions_list[tcount3] = strdup(vna_name_dupl);
                  versions_list[tcount3+1] = NULL;
               }
            }
            tcount2++;
         }
         fprintf(stderr, "   version: %s%s\n", vna_name_dupl, (patching) ? " (patched)" : "");
         free(vna_name_dupl);
      }
   }
   return versions_list;
}

char **getMatchingVersions(FILE *FP, ElfInfo *elfi, uint64_t verneed_offset,
   int verneed_count, uint64_t strings_offset, const char **version_strings)
{
   return getAndPatchMatchingVersions(FP, elfi, verneed_offset, verneed_count,
      strings_offset, version_strings, 0);
}

void patchMatchingVersions(FILE *FP, ElfInfo *elfi, uint64_t verneed_offset,
   int verneed_count, uint64_t strings_offset, int patchflag)
{
   char **slist = getAndPatchMatchingVersions(FP, elfi, verneed_offset, verneed_count,
      strings_offset, NULL, patchflag);
   int tcount = 0;
   while (slist[tcount])
   {
      free(slist[tcount]);
      tcount++;
   }
   free(slist);
}

void readVersionD(FILE *FP)
{
   const char *cposvers[] = { "GLIBC_2.2.5", "GLIBC_2.3", NULL };
   //cposvers[0] = "GLIBC_2.2.5";
   int version_r = -1;
   uint64_t version_r_offset = -1;
   int verneed_entries = 0;
   int linked_string_section = -1;
   uint64_t linked_string_section_offset = -1;
   fseek(FP, 0, SEEK_SET);
   ElfInfo elfi;
   if (ensureFileIsSYSV(FP, 0))
   {
      setMachineBits(FP, &elfi);
      getSectionHeaderTable(FP, &elfi);
      elfi.section_names_offset = findSectionOffset(FP, &elfi, elfi.section_index_section_names);
      version_r = findSection(FP, &elfi, ".gnu.version_r");
      if (version_r > 0)
         fprintf(stderr, "Found .gnu.version_r at section %d\n", version_r);
      version_r_offset = findSectionOffset(FP, &elfi, version_r);
      verneed_entries = getValueFromDynamic(FP, &elfi, 0x6fffffff); // DT_VERNEEDNUM
//      printf("verneed entries %d\n", verneed_entries);
      linked_string_section = findSectionLinkedStringSection(FP, &elfi, version_r);
      linked_string_section_offset = findSectionOffset(FP, &elfi, linked_string_section);
      char **version_strings = getMatchingVersions(FP, &elfi, version_r_offset, verneed_entries, linked_string_section_offset, cposvers);
      int tcount = 0;
      while ((version_strings) && (version_strings[tcount]))
      {
         printf("string %d: %s\n", tcount, version_strings[tcount]);
         free(version_strings[tcount]);
         tcount++;
      }
      free(version_strings);
   } else
   {
      fprintf(stderr, "Is not an ELF!");
   }
}

int main(int argc, char **argv)
{
   FILE *FP = fopen(argv[1], "rb");
   readVersionD(FP);
   fclose(FP);
}

